package de.breitbeil.jbooking.richclient.form.binding;

import java.awt.Component;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;

import javax.swing.ComboBoxEditor;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

import org.springframework.beans.BeansException;
import org.springframework.binding.form.FormModel;
import org.springframework.binding.value.support.RefreshableValueHolder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.closure.Closure;
import org.springframework.core.closure.Constraint;
import org.springframework.richclient.application.event.LifecycleApplicationEvent;
import org.springframework.richclient.form.binding.swing.AbstractListBinding;
import org.springframework.richclient.form.binding.swing.ComboBoxBinder;
import org.springframework.richclient.list.TextValueListRenderer;
import org.springframework.util.Assert;

import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.swing.AutoCompleteSupport;

import de.breitbeil.jbooking.dao.IGenericDao;
import de.breitbeil.jbooking.domain.Booking;
import de.breitbeil.jbooking.domain.Person;
import de.breitbeil.jbooking.richclient.form.binding.comboboxrenderer.ComboBoxRenderer;

public class ComboBoxDaoBinder extends ComboBoxBinder implements ApplicationContextAware, ApplicationListener {

    public static String EDITOR_RENDERER_COMPARATOR_KEY = "editorRendererKey";
    
	private IGenericDao genericDao;
	private final ComboBoxRenderer renderer;
	
	private RefreshableValueHolder values;
	private Class type;
		
	public ComboBoxDaoBinder(ComboBoxRenderer renderer) {
	    super();
	    this.renderer = renderer;
    }
	
    public void onApplicationEvent(ApplicationEvent event) {
        if(values != null && event instanceof LifecycleApplicationEvent) {
            LifecycleApplicationEvent le = (LifecycleApplicationEvent) event;
            
            if(type.isAssignableFrom(le.getObject().getClass())) {
                SwingUtilities.invokeLater(new Thread(){ 
                    public void run() {
                        values.refresh();
                    }});
            }
        }
    }
    
    public ApplicationEventMulticaster getApplicationEventMulticaster() {
        final String beanName = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME;
        ApplicationEventMulticaster applicationEventMulticaster = (ApplicationEventMulticaster) getApplicationContext().getBean(beanName);
        return applicationEventMulticaster;
    }
	
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        genericDao = (IGenericDao)applicationContext.getBean("genericDao");
    }
    
    protected AbstractListBinding createListBinding(JComponent control, FormModel formModel, String formPropertyPath) {
        Assert.isInstanceOf(JComboBox.class, control, formPropertyPath);
        return new ComboBoxDaoBinding((JComboBox) control, formModel, formPropertyPath, getRequiredSourceClass(), genericDao);
    }
	
	protected void applyContext(AbstractListBinding binding, Map context) {
	    if(context.size() == 0) {
	        context = new HashMap<String, Object>();
	    }
	    
	    ComboBoxDaoBinding comboBoxBinding = (ComboBoxDaoBinding) binding;
	    
	    // Set default context values. TODO: Maybe this would be better in BindingFactory!?
        if(!context.containsKey(RENDERER_KEY)) {
            context.put(ComboBoxBinder.RENDERER_KEY, new DaoComboboxRenderer(renderer));
        }
        if(!context.containsKey(COMPARATOR_KEY)) {
            context.put(COMPARATOR_KEY, renderer);
        }
        if(!context.containsKey(EDITOR_KEY)) {
            context.put(EDITOR_KEY, new DaoComboboxEditor(comboBoxBinding.getEditor(), renderer));
        } 
        if(!context.containsKey(EMPTY_SELECTION_VALUE)) {
            context.put(EMPTY_SELECTION_VALUE, null);
        }
        if(!context.containsKey(SELECTABLE_ITEMS_KEY)) {
            getApplicationEventMulticaster().addApplicationListener(this);
            
            context.put(SELECTABLE_ITEMS_KEY, createSelectableItems(comboBoxBinding, 
                    context.containsKey(EMPTY_SELECTION_VALUE), 
                    context.get(EMPTY_SELECTION_VALUE))); 
            
            /*DaoComboboxFilter filter = new DaoComboboxFilter(comboBoxBinding.getComboBox());
            context.put(FILTER_KEY, filter);*/
        }
        
        super.applyContext(binding, context);
    }
	
    private RefreshableValueHolder createSelectableItems(ComboBoxDaoBinding comboboxBinding, final boolean allowEmptySelection, final Object emptySelectionValue) {
        final String property = comboboxBinding.getProperty();
        final Class type = comboboxBinding.getFormModel().getFieldMetadata(property).getPropertyType();
        this.type = type;
        
        /*List<Object> selectableItems = new ArrayList<Object>();
        if(allowEmptySelection) {
            selectableItems.add(emptySelectionValue);
        }
        selectableItems.addAll(genericDao.findAll(type));*/
        
        values = new RefreshableValueHolder(new Closure() {
            public Object call(Object object) {
                List<Object> selectableItems = new ArrayList<Object>();
                if(allowEmptySelection) {
                    selectableItems.add(emptySelectionValue);
                }
                selectableItems.addAll(genericDao.findAll(type));
                return selectableItems;
            }
        }, false,false);
        
        return values;
    }
    
    protected class DaoComboboxRenderer extends TextValueListRenderer { 
        
        private ComboBoxRenderer renderer;
        
        public DaoComboboxRenderer(ComboBoxRenderer renderer) {
            this.renderer = renderer;
        }
        
        protected String getTextValue(Object value) {
            return renderer.getValue(value);
        }
    }
    
    /*
    protected class DaoComboboxFilter extends Observable implements Constraint, KeyListener {
        
        private JTextField editor;
        
        public DaoComboboxFilter(JComboBox combobox) {
            combobox.setEditable(true);
            Component editorComponent = combobox.getEditor().getEditorComponent();
            editor = (JTextField) editorComponent;
            editor.addKeyListener(this);
        }

        @Override
        public boolean test(Object argument) {
            System.out.println("Testing "+argument);
            // allow empty selection
            if(argument == null) {
                return true;
            }
            
            String item = renderer.getValue(argument);
            String input = editor.getText();
            
            return item.contains(input);
        }

        @Override
        public void keyPressed(KeyEvent e) {
        }

        @Override
        public void keyReleased(KeyEvent e) {
            e.consume();
            setChanged();
            notifyObservers();
            clearChanged();
        }

        @Override
        public void keyTyped(KeyEvent e) {
        }
    }*/
    
    protected class DaoComboboxEditor implements ComboBoxEditor {

        private Object current;
        private ComboBoxEditor innerEditor;
        private ComboBoxRenderer renderer;

        /**
         * Should only be used if the innerEditor will be set later
         *
         * @param renderedProperty
         */
        public DaoComboboxEditor(ComboBoxRenderer renderer) {
            this.renderer = renderer;
        }
        
        public DaoComboboxEditor(ComboBoxEditor innerEditor, ComboBoxRenderer renderer) {
            this.innerEditor = innerEditor;
            this.renderer = renderer;
        }

        /**
         * @see javax.swing.ComboBoxEditor#addActionListener(java.awt.event.ActionListener)
         */
        public void addActionListener(ActionListener l) {
            innerEditor.addActionListener(l);
        }

        /**
         * @see javax.swing.ComboBoxEditor#getEditorComponent()
         */
        public Component getEditorComponent() {
            return innerEditor.getEditorComponent();
        }

        /**
         * @see javax.swing.ComboBoxEditor#getItem()
         */
        public Object getItem() {
            return current;
        }

        /**
         * @see javax.swing.ComboBoxEditor#removeActionListener(java.awt.event.ActionListener)
         */
        public void removeActionListener(ActionListener l) {
            innerEditor.removeActionListener(l);
        }

        /**
         * @see javax.swing.ComboBoxEditor#selectAll()
         */
        public void selectAll() {
            innerEditor.selectAll();
        }

        /**
         * @see javax.swing.ComboBoxEditor#setItem(Object)
         */
        public void setItem(Object item) {
            current = item;
            if (item == null) {
                innerEditor.setItem("");
            } else {
                innerEditor.setItem(renderer.getValue(item));
            }
        }
    }

}
